//
// Created on 2025/6/15.
//
// Node APIs are not fully supported. To solve the compilation error of the interface cannot be found,
// please include "napi/native_api.h".

#ifndef VARINT_ENCODE_H
#define VARINT_ENCODE_H

#include "stdint.h"
#include <cstddef>
#include <type_traits>

#include "common.h"
#include "stream.h"

static bool varint_encode(uint64_t value, uint8_t *out, size_t *size) {
    uint32_t len = varint_bytes(value);
    if (*size < len)
        return false;
    *size = len--;
    uint32_t pos = 0;
    for (; pos < len; pos++) {
        out[pos] = (value & REST) | MSB;
        value >>= 7;
    }
    out[pos] = value & REST;
    return true;
}

static bool varint_encode_big_endian(uint64_t value, uint8_t *out, size_t *size) {
    uint32_t len = varint_bytes(value);
    if (*size < len)
        return false;
    *size = len--;
    uint32_t pos = len;
    for (; pos > 0; pos--) {
        out[pos] = (value & REST) | MSB;
        value >>= 7;
    }
    out[pos] = value & REST;
    return true;
}

template <typename T> static bool varint_encode_stream(varint_stream *stream) {
    using unsigned_type = typename std::make_unsigned<T>::type;

    while (stream->avail_in >= sizeof(T)) {
        const T val = *reinterpret_cast<const T *>(stream->next_in);
        unsigned_type value = zigzag_encode(val);
        uint32_t len = varint_bytes(value);

        if (stream->avail_out < (len--))
            return false;

        stream->next_in += sizeof(T);
        stream->avail_in -= sizeof(T);


        for (uint32_t pos = 0; pos < len; ++pos) {
            *stream->next_out++ = (value & REST) | MSB;
            stream->avail_out--;
            value >>= 7;
        }
        *stream->next_out++ = value & REST;
        stream->avail_out--;
    }

    return true;
}

template <typename T>
static bool varint_encode(const T *input, size_t input_size, uint8_t *output, size_t *output_size) {
    varint_stream vs;
    vs.next_in = reinterpret_cast<const uint8_t *>(input);
    vs.avail_in = input_size * sizeof(T);
    vs.next_out = output;
    vs.avail_out = *output_size;

    if (!varint_encode_stream<T>(&vs))
        return false;

    *output_size -= vs.avail_out;
    return true;
}


#endif // VARINT_ENCODE_H
